I need to register several methods of a class, but cannot do this in one call:
Therefore I use atm. something like this:
auto new_type = sol::usertype<player>("shoot", &player::shoot);
lua.set_usertype("player", new_type);
and another call:
auto new_type = sol::usertype<player>("boost", &player::boost);
lua.set_usertype("player", new_type);
I saw I could do this too:
auto new_type = sol::usertype<player>("boost", &player::boost);
lua.set_usertype("player", new_type);
lua["player"]["boost"] = &player::boost;
Now I wonder, what is the right approach for this?
There seems to be no method to check, whether a type is already registered or not.
PS: additional:
The ship example does not compile anymore:
http://sol2.readthedocs.io/en/latest/api/usertype.html
lua.set_usertype<ship>( "ship", shiptype );, should be changed to: lua.set_usertype( "ship", shiptype );
The primary example is here: https://github.com/ThePhD/sol2/blob/develop/examples/runtime_additions.cpp#L19
I'll fix the docs.
There is no way to check if a type has been "registered", except to get the table and see if it has certain members and fields. A simple check would be if my_type_name ~= nil then ... end.
If you want to do that with sol2, that's if (lua["my_type_name"] != sol::lua_nil) { ... }.
Prefer method 3 of what you proposed, or use the methods described in the examples.
Thanks for the help.
How do I add constructors via method 3?
sol::constructors<player(int)> lua_ctor;
lua["player"] = lua_ctor;
This seem not to work.
I need properties too(via getter/setter func)
@ThePhD
Any update here?
If you want constructors to use special syntax such as player(2), then you MUST register it as part of the usertype. Certain usertype features require that things be specified in advance so that a forwarding table can be modified: this includes anything that requires the metatable (calling constructors, properties, static var, etc.). If you don't bind it as such, you need to architect and engineer the metatable yourself (which you are welcome to do: that is how you create a class metatable all on your own).
Binding constructors "later" also doesn't make any sense. When you bind the type the constructors are known: bind everything you need from C++ in one go. Doing it in piecemeal fashion makes things incredibly hard, on me and you.
Otherwise, lua["player"]["new"] = ...; might help you with your constructor issue, presuming you want to use player.new.
@ThePhD
I want to use normal lua syntax, so using new
However following does not work:
sol::constructors<player(int)> lua_ctor;
lua["player"]["new"] = lua_ctor;
Exception:
"[string \"...\"]:2: attempt to call a sol.sol::constructor_list<player __cdecl(int,int)> value (field 'new')"
Why does it not make sense to add an constructor later at runtime?
With the current API I have here, I cannot create the ctor's, properties, or methods in one call.
It has to be multiple calls.
I wonder is this a limitation of Lua or your API?
EDIT
Okay, what seem to work is this:
lua["player"]["new"] = [](int foo){
return player(foo);
};
However, adding a new ctor fails.
lua["player"]["new"] = [](int foo, int bar) {
return player(foo, bar);
};
Okay, it seem to be not supported to add multiple constructors ad runtime.
Can you not add this feature?
lua.get_usertype<player>().add_constructor(....);
No, I can't add that feature right now. When sol2 serializes a usertype, it rips everything out of C++ and packs it into a specific representation which only lives in Lua. I have not structured it to be directly manipulatable through C++, except by using the typical conventions of Lua with adding function objects/fields to the entire class.
I will take this into consideration for the huge refactor I am doing of usertypes for both compilation time and ease of use, which is #538
You can have overloaded ctors using sol::overload( func1, func2, ... ). See here for how it works. Overloads must be collected into a single entity and resolved that way, because the syntax player.new = function ... end in Lua does not "add an overload", it completely overwrites the entry. My API needs to maintain parity with how Lua behaves, otherwise I will violate most user expectation.
@ThePhD
Thanks for your answer.
So is this a limitation of lua or could you implement it?
I guess my only option is to give new a new name:
lua["player"]["new"] = [](int foo) {
return player(foo);
};
lua["player"]["new2"] = [](int foo, int bar) {
return player(foo, bar);
};
However, what about adding, properties, inheritance and so on?
Or in general, what are the runtime binding limitation of sol2?
Would be interesting to know, whether this is a lua issue.
@ThePhD
Setting a property at "runtime" is also not possible:
lua.new_usertype<player>("player");
lua["player"]["hp"] = sol::property(&player::get_hp, &player::set_hp);
Or how do I do it?
I have to set this information all step by step and not in one big function call.
Properties cannot be set in this manner. Neither can inheritance. These C++ classes are present are compile-time. What is preventing you from binding them ahead-of-time?
Because I have a visitor which is visiting all methods and properties of a class at run time. But the visit methods are instantiated at compile time., otherwise I would not get the concrete type. Furthermore all constructors, methods and properties are visited one by one.
Just imagine this visit method:
template<typename type_list<Ctor_Args...>, typename type_list<Property_Acc....>, type_list<Method_Acc...>>
void visit(....);
Also regarding compile times, that is a nightmare. You cannot reuse already instantiated types.
Image you bind a method: void foo(); which is totally generic.
When you instantiate this in a call like above, it will be part of the function signature and a new function will be instantiated.
Instead, I have something like this:
template<typename Method_Acc>
void visit(Method_Acc acc, method_info info)
{
lua[info.get_name()] = acc;
}
}
I can bind hundred of void foo() functions. But only one visit method get instantiated.
Anyway, it means, I can practically only bind functions at runtime with your library.
What would be interesting for me, is this a limitation of Lua or of your wrapper?
This is a limitation of my wrapper. There are other wrappers which provide more runtime-oriented, simple bindings, and of course you can build anything out of Lua (it lets you orchestrate and set up calls to C -- and thusly, C++ -- functions to perform your work).
You can also create a metatable using sol2 and populate it with your own custom meta-type at that point as well, and also override sol2's sol::metamethod::index and sol::metamethod::new_index entries to provide your own custom handling of how things are interpreted/handled. You would instantiate a usertype with lua.new_usertype<thing>( "thing" );. Then, for your visitation, you'd first get the metatable of that with sol::table metatable = lua["thing"];.
Now, rather than modify entries directly, you'd build up a struct that handles lookup for you. When you were finally done, after all your visitations, you'd do
cpp
metatable[sol::metamethod::index] = a_lookup_function;
metatable[sol::metamethod::newindex] = an_insertion_or_overwrite_function;
I am working on a new way of binding to battle compile-time woes, which has been a pain point for users. In it, I'll likely be rolling out many of the features you want here. But I don't have a concrete ETA on that, since I've got a lot of other things I've been prioritizing higher (bugfixes and finally cleaning up the source snippets in the documentation).
I chose the way we currently do it for performance metrics, so if I'm going to do it this way I will also be preserving the same performance metrics. It just means that type registration might become a little bit more runtime expensive, and I might have to do some... interesting things, to get around type erasure and still be breakneck fast.
c.f. #612
@ThePhD
I did not found any lua binding, that support this. Or can you tell me what I have to use instead?
You're framework is atm. the best to bind lua to cpp. IMHO.
Do you have time, or show me reference, where I can see how I have to implement: a_lookup_function & an_insertion_or_overwrite_function?
Here's a cheap example that doesn't handle all of the complexities of a real usertype. There's a lot you'd have to do to essentially rewrite what are the internals of sol2. Note that you can, again, do this yourself with the Lua C API, but it would be even more difficult.
#define SOL_CHECK_ARGUMENTS 1
#include <sol.hpp>
struct thing {
int member_variable = 5.0;
double member_function() const {
return member_variable / 2.0;
}
};
#define TEMPLATE_AUTO(x) decltype(x), x
void register_thing_type(sol::state& lua) {
// cheap storage: in reality, you'd need to find a
// better way of handling this.
// or not! It's up to you.
static std::unordered_map<sol::string_view, sol::object> function_associations;
static std::unordered_map<sol::string_view, sol::object> variable_associations;
variable_associations.emplace_hint(variable_associations.cend(),
"member_variable", sol::object(lua.lua_state(), sol::in_place, &sol::c_call<TEMPLATE_AUTO(&thing::member_variable)>)
);
function_associations.emplace_hint(function_associations.cend(),
"member_function", sol::object(lua.lua_state(), sol::in_place, &sol::c_call<TEMPLATE_AUTO(&thing::member_function)>)
);
struct call_handler {
static int lookup_function(lua_State* L) {
sol::stack_object source(L, 1);
sol::stack_object key(L, 2);
if (!source.is<thing>()) {
return luaL_error(L, "given an incorrect object for this call");
}
sol::optional<sol::string_view> maybe_svkey = key.as<sol::string_view>();
if (maybe_svkey) {
{
// functions are different from variables
// functions, when obtain with the syntax
// obj.f, obj.f(), and obj:f()
// must return the function itself
// so we just push it realy into our target
auto it = function_associations.find(*maybe_svkey);
if (it != function_associations.cend()) {
return it->second.push(L);
}
}
{
// variables are different than funtions
// when someone does `obj.a`, they expect
// this __index call (this lookup function) to
// return to them the value itself they're seeing
// so we call out lua_CFunction that we serialized earlier
auto it = variable_associations.find(*maybe_svkey);
if (it != variable_associations.cend()) {
// note that calls generated by sol2
// for member variables expect the stack ordering to be
// 2(, 3, ..., n) -- value(s)
// 1 -- source
// so we destroy the key on the stack
sol::stack::remove(L, 2, 1);
lua_CFunction cf = it->second.as<lua_CFunction>();
return cf(L);
}
}
}
return sol::stack::push(L, sol::lua_nil);
}
static int insertion_function(lua_State* L) {
sol::stack_object source(L, 1);
sol::stack_object key(L, 2);
sol::stack_object value(L, 3);
if (!source.is<thing>()) {
return luaL_error(L, "given an incorrect object for this call");
}
// write to member variables, etc. etc...
sol::optional<sol::string_view> maybe_svkey = key.as<sol::string_view>();
if (maybe_svkey) {
{
// variables are different than funtions
// when someone does `obj.a`, they expect
// this __index call (this lookup function) to
// return to them the value itself they're seeing
// so we call out lua_CFunction that we serialized earlier
auto it = variable_associations.find(*maybe_svkey);
if (it != variable_associations.cend()) {
// note that calls generated by sol2
// for member variables expect the stack ordering to be
// 2(, 3, ..., n) -- value(s)
// 1 -- source
// so we remove the key value
sol::stack::remove(L, 2, 1);
lua_CFunction cf = it->second.as<lua_CFunction>();
return cf(L);
}
}
}
// exercise for reader:
// how do you override functions on the metatable with
// proper syntax, but error when the type is
// an "instance" object?
return 0;
}
};
lua.new_usertype<thing>("thing");
sol::table metatable = lua["thing"];
metatable[sol::meta_method::index] = &call_handler::lookup_function;
metatable[sol::meta_method::new_index] = &call_handler::insertion_function;
}
int main () {
sol::state lua;
lua.open_libraries(sol::lib::base);
register_thing_type(lua);
lua.script(R"(t = thing.new()
print(t.member_variable)
print(t:member_function())
t.member_variable = 24
print(t.member_variable)
print(t:member_function())
)");
return 0;
}
Now maintained as an official example: https://github.com/ThePhD/sol2/blob/develop/examples/metatable_customization.cpp
Most helpful comment
Here's a cheap example that doesn't handle all of the complexities of a real usertype. There's a lot you'd have to do to essentially rewrite what are the internals of sol2. Note that you can, again, do this yourself with the Lua C API, but it would be even more difficult.