ThePhD / sol2

Sol3 (sol2 v3.0) - a C++ <-> Lua API wrapper with advanced features and top notch performance - is here, and it's great! Documentation:
http://sol2.rtfd.io/
MIT License
4.06k stars 493 forks source link

Trying to bind a global variable (c++) to a lua variable #1498

Closed learn-more closed 1 year ago

learn-more commented 1 year ago

Is there a way to bind a lua variable to a c++ variable, so that when the lua variable is updated, the c++ variable follows?

Example code:

#include <sol/sol.hpp>

int g_Var = 0;

int main()
{
    sol::state lua;
    lua.open_libraries(sol::lib::base);

    lua["Var1"] = &g_Var;
    lua.script("print(Var1); Var1 = 1; print(Var1)");
    printf("Var1=%d\n", g_Var);

    lua.set("Var2", sol::var(std::ref(g_Var)));
    lua.script("print(Var2); Var2 = 2; print(Var2)");
    printf("Var2=%d\n", g_Var);

    return 0;
}

I was really hoping that the c++ printf would result in Var1=1 or Var2=2, but they print as Var1=0 and Var2=0.

Rochet2 commented 1 year ago

What you are after cannot exactly be done without some workarounds. Here are some alternatives what you try.

1) You can have a table/userdata that uses the __index and __newindex metamethods to get and set the value. This basically calls just functions that get/set the value on C++ side. Using sol you can basically for example make a dummy struct that acts as the userdata. The userdata then can get/set the value when you assign to a member of it. See how speed variable is used in this example between lua and c++ and how it is bound using sol2: https://sol2.readthedocs.io/en/latest/tutorial/cxx-in-lua.html Here is an example: https://godbolt.org/z/GTEohYEez

2) With some work you can try fake it so that the variable works only in global scope. You can do it with a similar trick as the first approach, but you abuse the global _G table so that when accessing global variables, the metamethods get and set the c++ value.

3) Use the first approach, but choose some other API. For example this kind of API is possible:

print(Var()) -- get
print(Var(56)) --set
learn-more commented 1 year ago

What you are after cannot exactly be done without some workarounds. Here are some alternatives what you try.

  1. You can have a table/userdata that uses the __index and __newindex metamethods to get and set the value. This basically calls just functions that get/set the value on C++ side. Using sol you can basically for example make a dummy struct that acts as the userdata. The userdata then can get/set the value when you assign to a member of it. See how speed variable is used in this example between lua and c++ and how it is bound using sol2: https://sol2.readthedocs.io/en/latest/tutorial/cxx-in-lua.html Here is an example: https://godbolt.org/z/GTEohYEez
  2. With some work you can try fake it so that the variable works only in global scope. You can do it with a similar trick as the first approach, but you abuse the global _G table so that when accessing global variables, the metamethods get and set the c++ value.
  3. Use the first approach, but choose some other API. For example this kind of API is possible:
print(Var()) -- get
print(Var(56)) --set

Thanks for your detailed answer! I have been trying to get 3 working, but all that I could find were examples how to do that on a custom usertype, not on a global var.

Sample seems to be the closest to what I want, is that something that could be useful for the docs?

Rochet2 commented 1 year ago

I have been trying to get 3 working, but all that I could find were examples how to do that on a custom usertype, not on a global var.

You can either define a function or you can define a usertype that uses the __call metamethod. I was first thinking about using the metamethods, but realized that they are not needed.

Here is an example for the third approach by using functions only: https://godbolt.org/z/59jMMEorj As you may see, it works for any type of primitive.

template<typename T>
auto Wrap(T& reference) {
    return [&](std::variant<T, sol::lua_nil_t> value) -> sol::optional<T> {
        if (const T* pValue = std::get_if<T>(&value)) {
            reference = *pValue;
            return {};
        } else {
            return reference;
        }
    };
}

// lua["luaVariableName"] = Wrap(someVariable); // Expose the C++ variable to lua through the Wrap function

The Wrap function takes a reference and makes a lambda that gets/sets the value based on if the parameter is nil or correct type value. This lambda that it returns is then exposed to lua where you call it to get/set the value. It also throws an error if you input invalid value, but its not so nice:

expected anything, received number: value does not fit any type present in the variant (bad argument into 'sol::optional<std::__cxx11::basic_string<char> >(std::variant<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, sol::lua_nil_t>)')

ps. Feels like implementing react hooks with sol here ... const [value, valueSetter] = useState(42); valueSetter(24); 😃

learn-more commented 1 year ago

Thank you!