sol2
sol2 copied to clipboard
How to pass sol usertype by value inside Lua?
Hi.
This is not a bug report but a question. Sorry if it is already answered somewhere. I didn't manage to find anything relevant in docs or issues.
I have a couple of user types registered through sol, like this:
_Session->GetScriptState().new_usertype<DEM::Game::HEntity>("HEntity"
, sol::constructors<sol::types<>, sol::types<const DEM::Game::HEntity&>>()
, sol::meta_function::to_string, &DEM::Game::EntityToString
, sol::meta_function::concatenation, sol::overload(
[](const char* a, DEM::Game::HEntity b) { return a + DEM::Game::EntityToString(b); }
, [](DEM::Game::HEntity a, const char* b) { return DEM::Game::EntityToString(a) + b; })
);
What is important about these types is that they are pointer sized or less and can be seen as opaque integer handles.
The problem arises when I want to catch a value of this type into a Lua closure, e.g.:
-- EntityID is a var of type HEntity, passed as an argument from C++, where it is stored on stack
OpenUI(function()
local SO = Session.World.SmartObject(EntityID)
if SO then
...
end
end)
EntityID in this example is stored by reference, and is deleted from C++ stack short after calling OpenUI because it goes out of scope. And when a closure is called, EntityID inside Lua is invalid.
An ugly workaround is possible but it requires constant attention from the programmer and it is also slower because of the copy construction:
EntityIDCopy = HEntity.new(EntityID)
OpenUI(function()
local SO = Session.World.SmartObject(EntityIDCopy)
if SO then
...
end
end)
What I expect from HEntity usertype is that it is passed by value everywhere. This should not be a problem technically because it is just a number wrapped into a convenience class, and it is trivially copyable. Then it will be stored by value in a closure too, remaining valid forever. It is also faster than allocating a copy of HEntity on the heap when I pass it from C++ to Lua.
How should I setup sol usertype to achieve this? AFAIR raw Lua's "light userdata" offered something like this.
AFAIR raw Lua's "light userdata" offered something like this
Maybe so, but if using lightuserdata you will not be able to have a metatable attached to the lightuserdata that contains methods. (my_light_userdata:Method())
EntityIDin this example is stored by reference, and is deleted from C++ stack short after callingOpenUIbecause it goes out of scope. And when a closure is called,EntityIDinside Lua is invalid.
Sounds like you are explicitly passing a pointer or an std::ref to sol even if you dont want to.
If I remember correctly, sol should just make a copy every time you put something into lua if you pass it by a normal reference.
Example: https://godbolt.org/z/9c9TWqese
Would it be possible to just not pass a pointer/std::ref to sol?
No, I definitely pass it not by pointer or explicit std::ref.
C++ side:
static void CallTransitionScript(sol::function& Script, HEntity EntityID, CStrID CurrState, CStrID NextState)
{
if (Script.valid())
{
auto Result = Script(EntityID, CurrState, NextState);
if (!Result.valid())
{
sol::error Error = Result;
::Sys::Error(Error.what());
}
}
}
...
CallTransitionScript(pSOAsset->GetScriptFunction(Lua, "OnTransitionEnd"), EntityID, SO.CurrState, SO.NextState);
Lua side:
function OnTransitionEnd(EntityID, PrevState, NewState)
ContainerID = HEntity.new(EntityID) -- FIXME: have to make a copy, because EntityID is destroyed in C++
OpenUI(EntityID, function()
local SO = Session.World.SmartObject(ContainerID)
...
end)
end
Note that EntityID is passed by value. sol::function instantiates a call operator with HEntity& but this decision doesn't come from user code.
Seems that passing by reference is done for functions. Function documentation describes this and how you can avoid it: https://sol2.readthedocs.io/en/latest/functions.html#functions-and-argument-passing
However, the way to avoid it was added for 4.0.0, which seems to be in alpha https://github.com/ThePhD/sol2/commit/561c90abf4e2106377cc1e29021ef60b4d6b9240
See SOL_FUNCTION_CALL_VALUE_SEMANTICS and is_value_semantic_for_function.
@niello by default sol::function passes lvalue reference arguments to lua by reference. Try to move arguments:
auto Result = Script(std::move(EntityID), std::move(CurrState), std::move(NextState));
@Rochet2 , thank you, this looks like exactly what I need. I will consider to move to 4.0 when it is released. @Smertig , thank you too. I will try this out of curiosity, but I don't want to move values because it is a per-call solution (which means you can easily forget to do it at some place) and because I want to continue using EntityID after passing its value to Lua.
is_value_semantic_for_function works in 3.3.0